#include "completer.h"
#include "readtags.h"

#include <vector>
#include <algorithm>
#include <tinyxml/tinyxml.h>

using namespace std;

CtagsCompleter::CtagsCompleter() {
	tfb = 0;
	hThread = INVALID_HANDLE_VALUE;
}

CtagsCompleter::~CtagsCompleter() {
}
namespace {
	typedef struct {
		char * path;
		CtagsCompleter * com;
	}  par_data ;

	void LoadDefaultXML(char* path, CtagsCompleter *com) {
		TiXmlDocument doc;
		doc.LoadFile(path);
		TiXmlNode *root = doc.FirstChild("default_include");
		if (!root)
			return;

		TiXmlNode *child = 0;
		while (child = root->IterateChildren("dir",child)) {
			com->LoadCommonFromPath(child->FirstChild()->Value());
		}
		com->initOver = true;
	}
	
	DWORD WINAPI threadProc( LPVOID data) {
		par_data * pt = (par_data *)data;
 		LoadDefaultXML(pt->path, pt->com);
		delete pt;
		return 0;
	}

}

void CtagsCompleter::Initialize( IPluginFacade* Cine ) {
	cine = Cine;
	
	GetCurrentDirectory(1000, buf_cmd);
	std::basic_string<TCHAR> cmd(buf_cmd);
	dpath = cmd;
	cmd += TEXT("\\ctags.exe -R");

	memcpy(buf_cmd, cmd.c_str(), (cmd.size() + 1)* 2);

	tfb = new TagFileBuffer();

	initOver = false;
	par_data *data = new par_data;
	data->com = this;
	data->path = "Plugins/Config/Completer.xml";
	hThread = CreateThread(NULL, 0, threadProc, data, 0, &tid);
}

void CtagsCompleter::Release(){
	if (hThread != INVALID_HANDLE_VALUE) {
		DWORD Ret;
		GetExitCodeThread(hThread, &Ret);
		if (Ret == STILL_ACTIVE) {
			TerminateThread(hThread, 0);
		}
		hThread = INVALID_HANDLE_VALUE;
	}
	if (tfb) {
		delete tfb;
		tfb = 0;
	}
	CtagsCompleter::Deactivate();
}

void CtagsCompleter::Activate() {
	cine->AttachListener(Events::CompletePrefixEvent::type(), this);
	cine->AttachListener(Events::CompleteFieldEvent::type(), this);
}

void CtagsCompleter::Deactivate() {
	cine->ReleaseListener(this);
}

bool CtagsCompleter::HandleEvent( Events::Event * event ) {
	if (event->GetType() == Events::CompletePrefixEvent::type()) {

		Events::CompletePrefixEvent * e = ( Events::CompletePrefixEvent *) event;
		std::string filename = GetTagsFile(e->path.c_str());
		e->wordList->clear();
		GetNearestTags(filename.c_str(), e->prefix.c_str(), e->wordList);
		filename = BuildTempTags(e->targetFile.c_str());
		GetNearestTags(filename.c_str(), e->prefix.c_str(), e->wordList);
		if (initOver && tfb) {
			tfb->GetNearestTags(e->prefix.c_str(), e->wordList);
		}
		return true;

	} else if (event->GetType() == Events::CompleteFieldEvent::type()) {

		Events::CompleteFieldEvent * e = (Events::CompleteFieldEvent *) event;
		e->wordList->clear();
		string filename = BuildTempTags(e->targetFile.c_str());
		string type = GetTagType(filename.c_str(), e->name.c_str());
		GetFieldTags(filename.c_str(), type, e->wordList);
		filename = GetTagsFile(e->path.c_str());
		GetFieldTags(filename.c_str(), type, e->wordList);
		if (initOver && tfb) {
			tfb->GetFieldTags(type, e->wordList);
		}
		unsigned int i, cnt = 0;
		for (i = 0; i < e->wordList->size(); ++i) {
			if ((*(e->wordList))[i][0] == '_') {
				++cnt;
			} else {
				if (cnt) {
					(*(e->wordList))[i - cnt] = (*(e->wordList))[i];
				}
			}
		}
		e->wordList->resize(e->wordList->size() - cnt);
		return true;

	}
	return false;
}

// ====== functions ====
std::string CtagsCompleter::GetTagsFile( const char* path ) {
	// check wether a tags file already here
	bool single_file = false;
	std::string res(path), path_s;
	if (!(GetFileAttributesA(path) &  FILE_ATTRIBUTE_DIRECTORY)) {
		int len = strlen(path);
		while (len > 0 && path[len] != '/' && path[len] != '\\') --len;
		res.erase(res.begin()+len, res.end());
		single_file = true;
	}
	path_s = res;
	if (!single_file) {
		res += "/tags";
	} else {
		res = path;
		res += ".tag";
	}
	FILE *fp = fopen(res.c_str(), "r");

	if (fp != NULL) {
		fclose(fp);
		return res;
	}
	
	// if not found, we try to create one

	TCHAR buf[1000];
	TCHAR cmd_buf[1000];

	int buflen = MultiByteToWideChar(CP_ACP, 0, path_s.c_str(), path_s.size(), buf, 1000);
	buf[buflen] = 0;

	if (single_file) {
		int buflen = MultiByteToWideChar(CP_ACP, 0, res.c_str(), res.size(), cmd_buf, 1000);
		cmd_buf[buflen] = 0;
		basic_string<TCHAR> cmd(buf_cmd);
		cmd.erase(cmd.size()-1);
		cmd += TEXT("f ");
		cmd += cmd_buf;
		buflen = MultiByteToWideChar(CP_ACP, 0, path, strlen(path), cmd_buf, 1000);
		cmd_buf[buflen] = 0;
		cmd += TEXT(" ");
		cmd += cmd_buf;

		memcpy(cmd_buf, cmd.c_str(), (cmd.size() + 1)* 2);
		if (!ExecuteShellCommand(cmd_buf, buf)) {
			DeleteFileA(res.c_str());
			res = "";
		}
	} else {
		if (!ExecuteShellCommand(buf_cmd, buf, 15000)) {
			DeleteFileA(res.c_str());
			res = "";
		}
	}

	return res;
}

bool CtagsCompleter::ExecuteShellCommand( LPTSTR commandLine, LPCTSTR workPath, unsigned int timeOut ) {
	STARTUPINFO             StartupInfo;
	ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
	StartupInfo.cb           = sizeof(STARTUPINFO);
	StartupInfo.dwFlags      = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	StartupInfo.wShowWindow  = SW_HIDE;

	PROCESS_INFORMATION     pi;
	if (!CreateProcess ( NULL, commandLine, NULL, NULL, TRUE, 0, NULL,
		workPath, &StartupInfo, &pi) ) {
		return false;
	}

	WaitForSingleObject(pi.hProcess, timeOut);
	DWORD Ret;
	GetExitCodeProcess(pi.hProcess, &Ret);
	if (Ret == STILL_ACTIVE) {
		TerminateProcess(pi.hProcess, 0);
		return false;
	}
	return true;
}


void CtagsCompleter::GetNearestTags( const char* tagfile, const char* prefix, vector<string>* wordList) {
	tagFileInfo info;
	tagFile* file = tagsOpen(tagfile, &info);
	vector<string> &nameList=*wordList;
	if (!info.status.opened) {
		file = 0;
		goto errorhandle;
	}

	tagEntry entry;
	if (tagsFind (file, &entry, prefix, TAG_PARTIALMATCH | TAG_IGNORECASE)==TagFailure) {
		goto errorhandle;
	}
	nameList.push_back(entry.name);

	while ( tagsFindNext (file, &entry) == TagSuccess) {
		nameList.push_back(entry.name);
	}

	sort(nameList.begin(), nameList.end());
	nameList.erase(unique(nameList.begin(), nameList.end()), nameList.end());

errorhandle:
	if (file) {
		tagsClose(file);
	}
}

std::string CtagsCompleter::GetTagType( const char* tagfile, const char * tag ) {
	string res;
	tagFileInfo info;
	tagFile* file = tagsOpen(tagfile, &info);
	if (!info.status.opened) {
		file = 0;
		goto endfunc;
	}	
	tagEntry entry;
	if (tagsFind (file, &entry, tag, TAG_FULLMATCH | TAG_IGNORECASE)==TagFailure) {
		goto endfunc;
	}
	
	unsigned i;
	if (entry.fields.count > 0) {
		for (i = 0; i < entry.fields.count; ++i) {
			if (entry.fields.list[i].key && strcmp(entry.fields.list[i].key, "typeref") == 0) {
				res = entry.fields.list[i].value;
				goto endfunc;
			}
		}
	}

	if (entry.address.pattern) {
		res = tryGetType(entry.address.pattern);
	}
	
endfunc:
	if (file) {
		tagsClose(file);
	}
	if (res.empty()) {
		res = tag;
	}
	int len = res.size();
	while (len > 0 && res[len] != ':') {
		--len;
	}
	if (len > 0) {
		res.erase(res.begin(), res.begin() + len + 1);
	}
	return res;
}

static bool isalpha(char c) {
	return c <= 'z' && c>='a' || c<='Z' && c>='A' || c <='9' && c>='0' || c == '_';
}

static bool iskeyword(const string & name) {
	return name == "static" || name == "register" || name == "auto" || name == "const"
		 || name == "struct" || name == "class";
}

std::string CtagsCompleter::tryGetType( const char* pattern) {
	string now;
	for (int i = 0; pattern[i]!='0'; ++i) {
		if (!isalpha(pattern[i])) {
			if (!now.empty()) {
				if (!iskeyword(now)) {
					return now;
				}
			}
			now = "";
		} else {
			now += pattern[i];
		}
	}
	return now;
}

void CtagsCompleter::GetFieldTags( const char* tagfile, std::string field, std::vector<std::string> *wordList ) {
	tagFileInfo info;
	tagFile* file = tagsOpen(tagfile, &info);
	if (!info.status.opened) {
		file = 0;
		goto endfunc;
	}

	tagEntry entry;
	if (tagsFirst(file, &entry) == TagFailure) {
		goto endfunc;
	}

	bool ok;
	unsigned i;
	do {
		ok = false;
		for (i = 0; i < entry.fields.count; ++i) {
			if (entry.fields.list[i].value == field) {
				ok = true;
				break;
			}
		}
		if (ok) {
			wordList->push_back(entry.name);
		}
	} while (tagsNext(file, &entry));

	vector<string> &nameList = *wordList;
	sort(nameList.begin(), nameList.end());
	nameList.erase(unique(nameList.begin(), nameList.end()), nameList.end());
	
endfunc:
	if (file) {
		tagsClose(file);
	}
}

std::string CtagsCompleter::BuildTempTags( const char* file ) {
	TCHAR buf_cmd[1000];
	TCHAR buf[1000];

	std::basic_string<TCHAR> cmd(dpath), target(dpath);
	cmd += TEXT("\\ctags.exe ");

	int buflen = MultiByteToWideChar(CP_ACP, 0, file, strlen(file), buf, 1000);
	buf[buflen] = 0;

	cmd += TEXT(" --c-types=+l");	//get local vars option

	target += TEXT("\\tmp_tags");
	cmd += TEXT(" -f ") + target + TEXT(" ");

	cmd += buf;

	memcpy(buf_cmd, cmd.c_str(), (cmd.size() + 1)* 2);
	memcpy(buf, dpath.c_str(), (dpath.size() + 1)* 2);
	
	ExecuteShellCommand(buf_cmd, buf);

	char sbuf[2048];
	int reslen = WideCharToMultiByte(CP_ACP, 0, target.c_str(), target.size(), sbuf, 2048, 0, 0);
	sbuf[reslen] = 0;

	return sbuf;
}

void CtagsCompleter::LoadCommonFromPath( const char * path ) {
	string tagFile = GetTagsFile(path);
	tfb->ReadTags(tagFile.c_str());
}
